package com.hsjk.szwj.module.mp.service;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.hsjk.szwj.framework.common.enums.UserTypeEnum;
import com.hsjk.szwj.framework.common.util.monitor.TracerUtils;
import com.hsjk.szwj.framework.common.util.servlet.ServletUtils;
import com.hsjk.szwj.framework.tenant.core.context.TenantContextHolder;
import com.hsjk.szwj.module.member.api.user.MemberUserApi;
import com.hsjk.szwj.module.member.api.user.dto.MemberCreateReqDTO;
import com.hsjk.szwj.module.member.api.user.dto.MemberUserRespDTO;
import com.hsjk.szwj.module.mp.controller.app.vo.AppAuthLoginRespVO;
import com.hsjk.szwj.module.mp.controller.app.vo.AppMiniQrCodeCreateReqVO;
import com.hsjk.szwj.module.mp.convert.MpConvert;
import com.hsjk.szwj.module.mp.dal.dataobject.MiniConfigDO;
import com.hsjk.szwj.module.mp.dal.dataobject.MpConfigDO;
import com.hsjk.szwj.module.mp.dal.mysql.IMiniMapper;
import com.hsjk.szwj.module.mp.dal.mysql.IMpMapper;
import com.hsjk.szwj.module.system.api.logger.LoginLogApi;
import com.hsjk.szwj.module.system.api.logger.dto.LoginLogCreateReqDTO;
import com.hsjk.szwj.module.system.api.oauth2.OAuth2TokenApi;
import com.hsjk.szwj.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import com.hsjk.szwj.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import com.hsjk.szwj.module.system.enums.logger.LoginLogTypeEnum;
import com.hsjk.szwj.module.system.enums.logger.LoginResultEnum;
import com.hsjk.szwj.module.system.enums.oauth2.OAuth2ClientConstants;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Objects;

import static com.hsjk.szwj.framework.common.util.servlet.ServletUtils.getClientIP;

/**
 * @author chenjiawei
 * @desc
 * @Date 2023/08/08 15:59
 **/
@Service
@Slf4j
public class MpServiceImpl implements IMpService {
    @Resource
    private IMpMapper mpMapper;

    @Resource
    private IMiniMapper miniMapper;

    @Resource
    private OAuth2TokenApi oauth2TokenApi;

    @Resource
    private LoginLogApi loginLogApi;

    @Resource
    private MemberUserApi memberUserApi;


    @Override
    public MpConfigDO getMp() {
        return mpMapper.getMp();
    }

    @Override
    public MpConfigDO getMpByTenantId(Long tenantId) {
        return mpMapper.getSystemMpByTenantId(tenantId);
    }

    @Override
    public MiniConfigDO getMiniByTenantId(Long tenantId) {
        return miniMapper.getMiniByTenantId(tenantId);
    }

    @Override
    public AppAuthLoginRespVO wxCallbackHandle(MemberCreateReqDTO dto) {
        MemberUserRespDTO member = memberUserApi.createMember(dto);
        if (ObjectUtil.isNotNull(member)) {
            AppAuthLoginRespVO tokenAfterLoginSuccess = createTokenAfterLoginSuccess(member, LoginLogTypeEnum.LOGIN_WXOPEN);
            tokenAfterLoginSuccess.setIsNew(member.getIsNew());
            return tokenAfterLoginSuccess;
        }
        return null;
    }

    @Override
    public String wxDecrypt(String encryptedData, JSONObject jsonObject, String vi)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        String sessionKey = (String) jsonObject.get("session_key");
        byte[] encData = cn.hutool.core.codec.Base64.decode(encryptedData);
        byte[] iv = cn.hutool.core.codec.Base64.decode(vi);
        byte[] key = Base64.decode(sessionKey);
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(encData), "UTF-8");
    }

    @Override
    public void createQrCode(AppMiniQrCodeCreateReqVO reqVO, HttpServletResponse response) {
        OutputStream outputStream = null;
        try {
            Long tenantId = TenantContextHolder.getTenantId();
            log.info("createQrCode:" + tenantId);
            MiniConfigDO miniConfig = this.getMiniByTenantId(tenantId);
            //调用工具包的服务
            WxMaService wxMaService = new WxMaServiceImpl();
            WxMaDefaultConfigImpl wxMaDefaultConfigImpl = new WxMaDefaultConfigImpl();
            //小程序appId
            wxMaDefaultConfigImpl.setAppid(miniConfig.getAppId());
            //小程序secret
            wxMaDefaultConfigImpl.setSecret(miniConfig.getAppSecret());
            wxMaService.setWxMaConfig(wxMaDefaultConfigImpl);
            /* 获取二维码字节数组 */
            byte[] bytes = wxMaService.getQrcodeService().createWxaCodeUnlimitBytes
                    (reqVO.getScene(), reqVO.getPage(), reqVO.getCheckPath(),
                            reqVO.getEnvVersion(), reqVO.getWidth(), reqVO.getAutoColor(), reqVO.getLineColor(),
                            reqVO.getIsHyaline());
            /* 设置返回文件类型，不设置前端识别不了 */
            response.setContentType(MediaType.IMAGE_JPEG_VALUE);
            outputStream = response.getOutputStream();
            outputStream.write(bytes);
            outputStream.flush();
        } catch (Exception ex) {
            log.info("生成小程序码失败，message:{}", ex.getMessage());
        } finally {
            try {
                outputStream.close();
            } catch (Exception ex) {
                log.error("关闭IO流失败,msg:{}", ex.getMessage());
            }
        }
    }

    @Override
    public String createMpQrCode(String sceneStr) {
        String ticket = null;
        Long tenantId = TenantContextHolder.getTenantId();
        MpConfigDO systemMp = this.getMpByTenantId(tenantId);
        WxMpService wxMpService = new WxMpServiceImpl();
        WxMpDefaultConfigImpl wxMpDefaultConfigImpl = new WxMpDefaultConfigImpl();
        //小程序appId
        wxMpDefaultConfigImpl.setAppId(systemMp.getAppId());
        //小程序secret
        wxMpDefaultConfigImpl.setSecret(systemMp.getAppSecret());
        wxMpService.setWxMpConfigStorage(wxMpDefaultConfigImpl);
        try {
            WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateLastTicket(sceneStr);
            ticket = wxMpQrCodeTicket.getTicket();
        } catch (WxErrorException e) {
            log.info("生成公众号码失败，message:{}", e.getMessage());
        }
        return ticket;
    }

    @Override
    public String getMpQrCodShortUrl(String ticket) {
        String url = null;
        Long tenantId = TenantContextHolder.getTenantId();
        MpConfigDO systemMp = this.getMpByTenantId(tenantId);
        WxMpService wxMpService = new WxMpServiceImpl();
        WxMpDefaultConfigImpl wxMpDefaultConfigImpl = new WxMpDefaultConfigImpl();
        //小程序appId
        wxMpDefaultConfigImpl.setAppId(systemMp.getAppId());
        //小程序secret
        wxMpDefaultConfigImpl.setSecret(systemMp.getAppSecret());
        wxMpService.setWxMpConfigStorage(wxMpDefaultConfigImpl);
        try {
            url = wxMpService.getQrcodeService().qrCodePictureUrl(ticket, true);
        } catch (WxErrorException e) {
            log.info("获取公众号二维码短连接URL失败，message:{}", e.getMessage());
        }
        return url;
    }

    @Override
    public String getMpQrCodLongUrl(String ticket) {
        String url = null;
        Long tenantId = TenantContextHolder.getTenantId();
        MpConfigDO systemMp = this.getMpByTenantId(tenantId);
        WxMpService wxMpService = new WxMpServiceImpl();
        WxMpDefaultConfigImpl wxMpDefaultConfigImpl = new WxMpDefaultConfigImpl();
        //小程序appId
        wxMpDefaultConfigImpl.setAppId(systemMp.getAppId());
        //小程序secret
        wxMpDefaultConfigImpl.setSecret(systemMp.getAppSecret());
        wxMpService.setWxMpConfigStorage(wxMpDefaultConfigImpl);
        try {
            url = wxMpService.getQrcodeService().qrCodePictureUrl(ticket);
        } catch (WxErrorException e) {
            log.info("获取公众号二维码长连接URL失败，message:{}", e.getMessage());
        }
        return url;
    }


    private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserRespDTO member, LoginLogTypeEnum logType) {
        // 插入登陆日志
        createLoginLog(member, logType, LoginResultEnum.SUCCESS);
        // 创建 Token 令牌
        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
                .setUserId(member.getId()).setUserType(getUserType().getValue())
                .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
        // 构建返回结果
        return MpConvert.INSTANCE.convertAppAuthLoginRespVO(accessTokenRespDTO);
    }

    private void createLoginLog(MemberUserRespDTO member, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
        // 插入登录日志
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setLogType(logType.getType());
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setUserId(member.getId());
        reqDTO.setUserType(getUserType().getValue());
        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserIp(getClientIP());
        reqDTO.setResult(loginResult.getResult());
        reqDTO.setUsername(member.getNickname());
        loginLogApi.createLoginLog(reqDTO);
        // 更新最后登录时间
        if (Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
            memberUserApi.updateUserLogin(member.getId(), getClientIP());
        }
    }

    private UserTypeEnum getUserType() {
        return UserTypeEnum.MEMBER;
    }
}
